/*! ******************************************************************************
 *
 * Pentaho
 *
 * Copyright (C) 2024 by Hitachi Vantara, LLC : http://www.pentaho.com
 *
 * Use of this software is governed by the Business Source License included
 * in the LICENSE.TXT file.
 *
 * Change Date: 2029-07-20
 ******************************************************************************/


package org.pentaho.metaverse.locator;

import org.pentaho.dictionary.DictionaryConst;
import org.pentaho.dictionary.DictionaryHelper;
import org.pentaho.metaverse.api.IDocumentEvent;
import org.pentaho.metaverse.api.IDocumentListener;
import org.pentaho.metaverse.api.IDocumentLocator;
import org.pentaho.metaverse.api.IMetaverseBuilder;
import org.pentaho.metaverse.api.IMetaverseNode;
import org.pentaho.metaverse.api.INamespace;
import org.pentaho.metaverse.api.MetaverseLocatorException;
import org.pentaho.metaverse.api.Namespace;
import org.pentaho.metaverse.impl.MetaverseCompletionService;
import org.pentaho.metaverse.messages.Messages;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Future;

/**
 * Base implementation for all @see IDocumentLocator implementations
 *
 * @param <T> The type of this locator
 * @author jdixon
 */
public abstract class BaseLocator<T> implements IDocumentLocator {

  private static final long serialVersionUID = 693428630030858039L;

  private static final Logger LOG = LoggerFactory.getLogger( BaseLocator.class );

  /**
   * The node in the metaverse that represents the root of this located domain/namespace
   */
  protected IMetaverseNode locatorNode;

  /**
   * The user session to use for the locator to use
   */
  protected IPentahoSession session;

  /**
   * The metaverse builder for adding this locator and its documents to
   */
  protected IMetaverseBuilder metaverseBuilder;

  /**
   * The unique id of the locator
   */
  protected String id = "";

  /**
   * The unique type of the locator
   */
  protected String locatorType;

  /**
   * The runner for this locator so that the document location is asynchronous
   */
  protected LocatorRunner<T> runner;

  /**
   * The completion service to use. This tracks the execution of a locator scan.
   */
  protected MetaverseCompletionService completionService = MetaverseCompletionService.getInstance();

  /**
   * The result of the scan of this locator
   */
  protected Future<String> futureTask;

  /**
   * A list of listeners to notify when document events are generated by this locator
   */
  protected List<IDocumentListener> listeners = new ArrayList<IDocumentListener>();

  /**
   * Constructor for the abstract super class
   */
  public BaseLocator() {
    DictionaryHelper.registerEntityType( DictionaryConst.NODE_TYPE_LOCATOR );
  }

  /**
   * Constructor that takes in a List of IDocumentListeners
   *
   * @param documentListeners the List of listeners
   */
  public BaseLocator( List<IDocumentListener> documentListeners ) {
    this.listeners = documentListeners;
  }

  /**
   * A method that returns the payload (object or XML) for a document
   *
   * @param locatedItem item to harvest; ie., a file
   * @return The object or XML payload
   * @throws Exception When the document contents cannot be retrieved
   */
  protected abstract Object getContents( T locatedItem ) throws Exception;

  @Override
  public void addDocumentListener( IDocumentListener listener ) {
    listeners.add( listener );
  }

  @Override
  public void notifyListeners( IDocumentEvent event ) {
    for ( IDocumentListener listener : listeners ) {
      listener.onEvent( event );
    }
  }

  @Override
  public void removeDocumentListener( IDocumentListener listener ) {
    listeners.remove( listener );
  }

  public String getRepositoryId() {
    return id;
  }

  public void setRepositoryId( String id ) {
    this.id = id;
  }

  public String getLocatorType() {
    return locatorType;
  }

  public void setLocatorType( String locatorType ) {
    this.locatorType = locatorType;
  }

  public IMetaverseBuilder getMetaverseBuilder() {
    return metaverseBuilder;
  }

  public void setMetaverseBuilder( IMetaverseBuilder metaverseBuilder ) {
    this.metaverseBuilder = metaverseBuilder;
  }

  /**
   * Returns the locator node for this locator. The locator node is the node in the metaverse
   * that represents this locator. It is used to create a link from this locator to the documents
   * that are found by/within it.
   *
   * @return The locator node in the metaverse
   */
  public IMetaverseNode getLocatorNode() {

    if ( locatorNode == null ) {

//      locatorNode = metaverseBuilder.getMetaverseObjectFactory().createNodeObject(
//          getNamespace().getNamespaceId(),
//          getRepositoryId(),
//          DictionaryConst.NODE_TYPE_LOCATOR );

      final URI uri = getRootUri();
      Map<String, Object> props = new HashMap<String, Object>() {
        {
          put( DictionaryConst.PROPERTY_NAME, getRepositoryId() );
          put( DictionaryConst.PROPERTY_TYPE, DictionaryConst.NODE_TYPE_LOCATOR );
          if ( uri != null ) {
            put( "url", uri.normalize().toString() );
          }
          // TODO get the description from somewhere else (each locator, e.g.)
          put( DictionaryConst.PROPERTY_DESCRIPTION, "This is a locator of documents to be analyzed" );
        }
      };

      locatorNode = metaverseBuilder.getMetaverseObjectFactory().createNodeObject( getNamespace(),
        DictionaryConst.LOGICAL_ID_GENERATOR_LOCATOR, props );

    }

    return locatorNode;
  }

  public void setLocatorNode( IMetaverseNode locatorNode ) {
    this.locatorNode = locatorNode;
  }

  protected INamespace getNamespace() {

    IMetaverseNode locatorNode = metaverseBuilder.getMetaverseObjectFactory().createNodeObject(
        UUID.randomUUID().toString(),
        getRepositoryId(),
        DictionaryConst.NODE_TYPE_LOCATOR );
    locatorNode.setLogicalIdGenerator( DictionaryConst.LOGICAL_ID_GENERATOR_LOCATOR );

    return new Namespace( locatorNode.getLogicalId() );
  }

  @Override
  public void stopScan() {
    if ( futureTask == null || futureTask.isDone() || futureTask.isCancelled() ) {
      // already stopped
      return;
    }

    LOG.debug( Messages.getString( "DEBUG.Locator.StopScan", getLocatorType() ) );

    runner.stop();
    futureTask.cancel( false );
    futureTask = null;
    runner = null;
  }

  /**
   * Starts a full scan by this locator.
   *
   * @param locatorRunner The locator runner to use
   * @throws MetaverseLocatorException
   */
  protected void startScan( LocatorRunner<T> locatorRunner ) throws MetaverseLocatorException {

    if ( futureTask != null && !futureTask.isDone() ) {
      throw new MetaverseLocatorException( Messages.getString( "ERROR.BaseLocator.ScanAlreadyExecuting" ) );
    }

    IMetaverseNode node = getLocatorNode();
    Date lastRun = new Date();
    node.setProperty( "lastScan", Long.toString( lastRun.getTime() ) );

    metaverseBuilder.addNode( node );

    runner = locatorRunner;
    runner.setLocator( this );

    LOG.debug( Messages.getString( "DEBUG.Locator.StartScan", getLocatorType() ) );

    futureTask = completionService.submit( runner, node.getStringID() );
  }

}
